#!/usr/bin/env python3
#
# Centos aarch64 image
#
# Copyright 2020 Linaro
#
# Authors:
#  Robert Foley <robert.foley@linaro.org>
#  Originally based on ubuntu.aarch64
#
# This code is licensed under the GPL version 2 or later.  See
# the COPYING file in the top-level directory.
#

import os
import sys
import subprocess
import basevm
import time
import traceback
import aarch64vm

DEFAULT_CONFIG = {
    'cpu'          : "max",
    'machine'      : "virt,gic-version=max",
    'install_cmds' : "yum install -y make ninja-build git python3 gcc gcc-c++ flex bison, "\
        "yum install -y glib2-devel pixman-devel zlib-devel, "\
        "yum install -y perl-Test-Harness, "\
        "alternatives --set python /usr/bin/python3, "\
        "sudo dnf config-manager "\
        "--add-repo=https://download.docker.com/linux/centos/docker-ce.repo,"\
        "sudo dnf install --nobest -y docker-ce.aarch64,"\
        "systemctl enable docker",
    # We increase beyond the default time since during boot
    # it can take some time (many seconds) to log into the VM.
    'ssh_timeout'  : 60,
}

class CentosAarch64VM(basevm.BaseVM):
    name = "centos.aarch64"
    arch = "aarch64"
    login_prompt = "localhost login:"
    prompt = '[root@localhost ~]#'
    image_name = "CentOS-8-aarch64-1905-dvd1.iso"
    image_link = "http://mirrors.usc.edu/pub/linux/distributions/centos/8.0.1905/isos/aarch64/"
    image_link += image_name
    BUILD_SCRIPT = """
        set -e;
        cd $(mktemp -d);
        sudo chmod a+r /dev/vdb;
        tar --checkpoint=.10 -xf /dev/vdb;
        ./configure {configure_opts};
        make --output-sync {target} -j{jobs} {verbose};
    """
    def set_key_perm(self):
        """Set permissions properly on certain files to allow
           ssh access."""
        self.console_wait_send(self.prompt,
                               "/usr/sbin/restorecon -R -v /root/.ssh\n")
        self.console_wait_send(self.prompt,
                "/usr/sbin/restorecon -R -v "\
                "/home/{}/.ssh\n".format(self._config["guest_user"]))

    def create_kickstart(self):
        """Generate the kickstart file used to generate the centos image."""
        # Start with the template for the kickstart.
        ks_file = self._source_path + "/tests/vm/centos-8-aarch64.ks"
        subprocess.check_call("cp {} ./ks.cfg".format(ks_file), shell=True)
        # Append the ssh keys to the kickstart file
        # as the post processing phase of installation.
        with open("ks.cfg", "a") as f:
            # Add in the root pw and guest user.
            rootpw = "rootpw --plaintext {}\n"
            f.write(rootpw.format(self._config["root_pass"]))
            add_user = "user --groups=wheel --name={} "\
                       "--password={} --plaintext\n"
            f.write(add_user.format(self._config["guest_user"],
                                    self._config["guest_pass"]))
            # Add the ssh keys.
            f.write("%post --log=/root/ks-post.log\n")
            f.write("mkdir -p /root/.ssh\n")
            addkey = 'echo "{}" >> /root/.ssh/authorized_keys\n'
            addkey_cmd = addkey.format(self._config["ssh_pub_key"])
            f.write(addkey_cmd)
            f.write('mkdir -p /home/{}/.ssh\n'.format(self._config["guest_user"]))
            addkey = 'echo "{}" >> /home/{}/.ssh/authorized_keys\n'
            addkey_cmd = addkey.format(self._config["ssh_pub_key"],
                                       self._config["guest_user"])
            f.write(addkey_cmd)
            f.write("%end\n")
        # Take our kickstart file and create an .iso from it.
        # The .iso will be provided to qemu as we boot
        # from the install dvd.
        # Anaconda will recognize the label "OEMDRV" and will
        # start the automated installation.
        gen_iso_img = 'genisoimage -output ks.iso -volid "OEMDRV" ks.cfg'
        subprocess.check_call(gen_iso_img, shell=True)

    def wait_for_shutdown(self):
        """We wait for qemu to shutdown the VM and exit.
           While this happens we display the console view
           for easier debugging."""
        # The image creation is essentially done,
        # so whether or not the wait is successful we want to
        # wait for qemu to exit (the self.wait()) before we return.
        try:
            self.console_wait("reboot: Power down")
        except Exception as e:
            sys.stderr.write("Exception hit\n")
            if isinstance(e, SystemExit) and e.code == 0:
                return 0
            traceback.print_exc()
        finally:
            self.wait()

    def build_base_image(self, dest_img):
        """Run through the centos installer to create
           a base image with name dest_img."""
        # We create the temp image, and only rename
        # to destination when we are done.
        img = dest_img + ".tmp"
        # Create an empty image.
        # We will provide this as the install destination.
        qemu_img_create = "qemu-img create {} 50G".format(img)
        subprocess.check_call(qemu_img_create, shell=True)

        # Create our kickstart file to be fed to the installer.
        self.create_kickstart()
        # Boot the install dvd with the params as our ks.iso
        os_img = self._download_with_cache(self.image_link)
        dvd_iso = "centos-8-dvd.iso"
        subprocess.check_call(["cp", "-f", os_img, dvd_iso])
        extra_args = "-cdrom ks.iso"
        extra_args += " -drive file={},if=none,id=drive1,cache=writeback"
        extra_args += " -device virtio-blk,drive=drive1,bootindex=1"
        extra_args = extra_args.format(dvd_iso).split(" ")
        self.boot(img, extra_args=extra_args)
        self.console_wait_send("change the selection", "\n")
        # We seem to need to hit esc (chr(27)) twice to abort the
        # media check, which takes a long time.
        # Waiting a bit seems to be more reliable before hitting esc.
        self.console_wait("Checking")
        time.sleep(5)
        self.console_wait_send("Checking", chr(27))
        time.sleep(5)
        self.console_wait_send("Checking", chr(27))
        print("Found Checking")
        # Give sufficient time for the installer to create the image.
        self.console_init(timeout=7200)
        self.wait_for_shutdown()
        os.rename(img, dest_img)
        print("Done with base image build: {}".format(dest_img))

    def check_create_base_img(self, img_base, img_dest):
        """Create a base image using the installer.
           We will use the base image if it exists.
           This helps cut down on install time in case we
           need to restart image creation,
           since the base image creation can take a long time."""
        if not os.path.exists(img_base):
            print("Generate new base image: {}".format(img_base))
            self.build_base_image(img_base);
        else:
            print("Use existing base image: {}".format(img_base))
        # Save a copy of the base image and copy it to dest.
        # which we will use going forward.
        subprocess.check_call(["cp", img_base, img_dest])

    def boot(self, img, extra_args=None):
        aarch64vm.create_flash_images(self._tmpdir, self._efi_aarch64)
        default_args = aarch64vm.get_pflash_args(self._tmpdir)
        if extra_args:
            extra_args.extend(default_args)
        else:
            extra_args = default_args
        # We always add these performance tweaks
        # because without them, we boot so slowly that we
        # can time out finding the boot efi device.
        if '-smp' not in extra_args and \
           '-smp' not in self._config['extra_args'] and \
           '-smp' not in self._args:
            # Only add if not already there to give caller option to change it.
            extra_args.extend(["-smp", "8"])
        # We have overridden boot() since aarch64 has additional parameters.
        # Call down to the base class method.
        super(CentosAarch64VM, self).boot(img, extra_args=extra_args)

    def build_image(self, img):
        img_tmp = img + ".tmp"
        self.check_create_base_img(img + ".base", img_tmp)

        # Boot the new image for the first time to finish installation.
        self.boot(img_tmp)
        self.console_init()
        self.console_wait_send(self.login_prompt, "root\n")
        self.console_wait_send("Password:",
                               "{}\n".format(self._config["root_pass"]))

        self.set_key_perm()
        self.console_wait_send(self.prompt, "rpm -q centos-release\n")
        enable_adapter = "sed -i 's/ONBOOT=no/ONBOOT=yes/g'" \
                         " /etc/sysconfig/network-scripts/ifcfg-enp0s1\n"
        self.console_wait_send(self.prompt, enable_adapter)
        self.console_wait_send(self.prompt, "ifup enp0s1\n")
        self.console_wait_send(self.prompt,
                               'echo "qemu  ALL=(ALL) NOPASSWD:ALL" | '\
                               'sudo tee /etc/sudoers.d/qemu\n')
        self.console_wait(self.prompt)

        # Rest of the commands we issue through ssh.
        self.wait_ssh(wait_root=True)

        # If the user chooses *not* to do the second phase,
        # then we will jump right to the graceful shutdown
        if self._config['install_cmds'] != "":
            install_cmds = self._config['install_cmds'].split(',')
            for cmd in install_cmds:
                self.ssh_root(cmd)
        self.ssh_root("poweroff")
        self.wait_for_shutdown()
        os.rename(img_tmp, img)
        print("image creation complete: {}".format(img))
        return 0

if __name__ == "__main__":
    defaults = aarch64vm.get_config_defaults(CentosAarch64VM, DEFAULT_CONFIG)
    sys.exit(basevm.main(CentosAarch64VM, defaults))
